package org.red5.server.net.rtmp;

import java.util.Arrays;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.commons.codec.binary.Hex;
import org.apache.mina.core.buffer.IoBuffer;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.red5.io.utils.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Tests for server side handshaking. Test command for using rtmpdump, if targeting specific player version use: -f "WIN 10,0,2,0"
 *
 * <pre>
 * rtmpdump -V -z -r "rtmp://localhost/live" -a "live" -W "http://localhost:5080/demos/publisher.swf" -p "http://localhost:5080/live" -y "test" -v -o t.flv
 * rtmpdump -V -z -r "rtmpe://localhost/live" -a "live" -W "http://localhost:5080/demos/publisher.swf" -p "http://localhost:5080/live" -y "test" -v -o t.flv
 * rtmpdump -V -z -r "rtmp://localhost/oflaDemo" -a "oflaDemo" -W "http://localhost:5080/demos/publisher.swf" -p "http://localhost:5080/oflaDemo" -y "test" -v -o t.flv -f "WIN 10,0,7,2"
 * </pre>
 *
 * Test server
 *
 * <pre>
 * rtmpsrv - V - z
 * </pre>
 *
 * @author Paul Gregoire (mondain@gmail.com)
 */
public class ServerRTMPHandshakeTest {

    private Logger log = LoggerFactory.getLogger(ServerRTMPHandshakeTest.class);

    // Red5 client generated C1
    // Time and version: 0000000580000702
    // Algorithm: 0
    // Offset: 460
    // Digest: a143cbbc7b8b4838496f2667402c1bd742e6c73d051e94a284519d8d7966c6c6
    private String red5clientC1 = "0000000580000702dc0578676cf5adfbf9be471bd4903991ae3d34421109e4d6612e5613100c31e9d3842743689dd57b5f675dd8af1081fbc24c266873044fa372045a1a32e9f08cc974ec3b1152f898893fb7b91ee9d45a8a84c7125e77447b9273e9caff90b240a59985355a30ac6650a3363baf6e34aeb6fc78ecc8a7f746711980e4d83d45248191eb9458f52338df757d7fb60c9e876c965c5907da9f8956bc0afa21698bc92d67962bc65e6d5c36383077cad969e1c336519659396c1d9949a4f1c4279a02a9f4f26d821beb92a86ff4bc463cb2d749d14b545e50dc052a248426af96b7ccae15321d0d4cec0d5d5c6543c99e7cd37e42d71098117f6a09c3f7a351570acf26aadba70f9383f3d1ff789fb63494f956fac4fbf26f5a4acb9f8131211c0c2cb068cfc7d2b5ff6e53730fcfb5ce483bbb76587136877d7e1c757711174511751d819d4fc2cca78f878ea6d830bf833b098e77ef95547f3037cdb964318c7767f60e61acf3039d63e5dd6b86d7cee4569189a9f324ffebff70e05e1ff0c14dbbf35b865c9beb03f837e926cc1d41d9811610ef5e59b0e2afabc36a5bd89986b385f2267f9358d7291fcab51cb8e8de5b52e0bcd392fc655ce1e6ee9ff38608504d80e5aaa143cbbc7b8b4838496f2667402c1bd742e6c73d051e94a284519d8d7966c6c612a0c2814e03344821dafeb196df6cf9f130a2718e9b88af7bc57e4410e72a38014017aa7a12fa53312ed4cc53f2bc73c5e450b1dba68d380e5bd0ded42165e512a13ad401c25add1b658ba1303b7a6bf48b9abfd2121cce20b7f02f618c7ae5d1c62c3826c64ee66b81156c2c4e8d8ccfa01e6352709c27cf259c1edd3993af730c8ed9027c0136efdf135e3646bebe8a4bd8b44c32eb71bc2d7473a4bfb3454bf5cc1801b0a5883bbcc724b42fbb83bdba881a455c23108bdc12e6c4c7afa2568d0d8161b4ac1126c5bd96fd4291f6e40be4e5d780a01067238b578073ec3bb0596c7ab6c7593c4bc1a79edfee22fae0c342d83010d2c77b43e8f4ce8d1c331dd97e1a69cbeb5b894316f519b3648b75d7d94091f6b9db7a4a7efcd98d606f819433e7344ad729846f04ffe2ca6a3bca43bb69d08a30cc189f1da97f69749868bf7302a7ceca8521ce673361a7918cec36218ed6cc47ad8e9d7b56d337d5d97f6828dfa78977e80e3236be323a4b4d4ac8c5a222f4675a1a44624a9ca30867193a8552e948585f38ac93b3e70f5b123a4149cf454effe17cef78aed52d70f5abcaba8e7bbdf0b452901a0a853a8ba173f5e895646ddf4a7b27b82e2b4052c3507d72563c123f9e55896fbf030c247591ab084db9a6a11d5e7bda60820f034f45f2b5825fd25963fabd93fecea0b07f959550bc12e1ac4d746e5c91704e57946e0724c7ec1f747e4205a7db483adafe62d9616551b3b6368c74ddd2c1afca81d068957a3f3ac219ef2a0c3d4469647b41a9633eead2d9647dfef334f138045a15b1920c8754f829084421d68c08940b5eecea6a68d29bc6a096c6d7b564a2e10b1a3eba48bc5d205913feab5c078b8c4877fecde82f7cb9510dfc297215798c25bd9207da4faed4f07dea78e40363ce75d439269cb3e61a71b5d4fec457ab58f8631b7ce7444454a10f7f67c9b5109ebc9c50c3491ea328e7b07e52f9f44aa9bfe88224f13e44b9838fa10aa4285a2fd95f74cac9271c851a4df78294d5673dd826a064cab5c50971a4cf2b81c669a8ec1c5f6d8acd1725796f0b7cca33b953457bdfb719ac31648b8969056232c877fa4a046e0ef1ba45f31149d5089e4a262cd52b18dcb5516bb2d2eec2baf2c6ac6b712ac76946f2ca7fcff3286bc9d62c5756b9e5e14ec9f90f415fadf3eba5c18a506f58c6929a8f96804efb4634baffb58353544b81f3a18ca7313ee8aafd25099a7802e23a0242b4e7cb61a098a748d705119513dc5e6459df926f6b7f385f7a3953b68f219e60da6df839b32e88e974f98e2d85b9615469ccde04c06624c4d62c439933d60bcc0fef2e286ebf3165e5cf1530bfd58097f271853220c6e7affaec85844613bb2cda966ac6f486dca0a9bf49523535a249f116ce0038f5e2872566c9d6d88cd81bcaccc7631be78248e3c44832305b4655a47a2a01a79848177c8059c3";

    // Chrome version 47.0.2526.111 (64-bit) FP version: 20.0.0.267
    private String chromeFlashPlayerC1 = "0000068e8000070282a38d183ee941036bd4fdf6b99369ee3f9b2af7f271a28c135329e29bde85bf67fd607fc566160fb62aeb4f190b935c711a6acf1111fa8bea7c677f78b84e2b6e373a931414727350535544221361fc9180aa71bacbcdf94ef6019038c03e06da0a0069d9e1cd7fcd275ae5f389683a835471fd0fb47cfde79af2bafefe3865a1e5cdd8b8a118a742f48ed1326ff84249de2f9f319f9efd672fde65a0ada9a3e57305d30efdb5cecb6924d5a7c8b36cf9a60c53be6c53077d2cda7ac3eeed8c29b40e0b3e06f0bfe7f167aa1c37f7f774b4c7dbb8721b76c6a22fe9c12640b824a8c88caaa5b2b0df3555504fb180b450a4c2b67570873de12c5790e501c05584fc3232103ce8e33ecc294b32356ec0d803903deeac831e5b7bae67626c8f403b664f9ab4abd936c31be19fcdac191faaea6dc177a9513fdddfedf43678663e149a6ca8abf57cc39049436bd14e5bf16f385733c053186b69a096acb0e2f8db7ed5e67cbfc9ab91165fb3b7911c7f6f97c8f667dbe6588fd93fbd77b418fd57557b9adba503c17f23237d84fde0d1458d4295de23bec4da78fd2722f3746213aa6f9a4a8159c0545129aeecd12803b419e31d49b9bb2183a46e90e5231c65c2b8b53ada899f3a271cdf37690c166b1719374ebaddd727e95d94df74e02bb375dc194a92264965c4a7a94b5e26b4fb1e7dfc488fd1156c83906cda5ce20dabefbaf8085c92a45cd373e856ee1baed027068c4f3d3694c2c2f31d85b445b416bc97222fa7fd9f5ee43d583d360c5223de8894beda51c9ae457fa5e6af9fb12c97927b3123da3bec972bfccedc5b811931fb7fd57d4392ab5b7776833f0b0e9e80138b161e62f65daec5abbec03e48572f8185b787c19b91eacdc0c12dff4d0fb0a0a0cfe78581ffcf32159b2e949ad40f6eee574233f5c1faa955e3f6f0ac232c0b4c6443b8d5e48f0531cdcaae2ff52d4d3def1bf174f12bd093057dfe9170ca6e717d2f3043c998d3a20ed5ddb80815510647aa06c672ba45bdd9ee282b337ceef0c2eb9867ee1d65bb251d0695afc1778ed258953af0b1752f78babc2a0643f7df068b5dea9344d667facf4ab9e9073cf235260bbbe23618118e7b1fedabd106c7e9c786b780ca22254694f3614374a840dcdfa32bdf44d1ec1f0128ebb8d988d350bc75e286fbf9142d19c4e2f450dcadfacaa0c380c6e3c34cd61be34bf83cf793ab0f84fe2ab6c9d6a4676decab9c6aac0e3ae7da6808b63b8cd048b44c9f7d432675632d7a553a95a47126e987e2d53d948672d12e8a2953d7cae8611690907b1f32e98f754c29ac7c2b3fecb8e1f70bc71a38a37a82e18909c52476e34d3eb3e7d5f03035b76b3af6e50c8f7ba58ec9b29d0b34d538502ecc57f1093044654df78768e6edef7627f700b4724ac2e4d0ae3d1ef87364e50d88bda26808b4e5ae4ec9732d5deb29f0d367173e1be0a3a2c94cec282a8c636fd1fa179f23da3ad2555611c182ef63e8bc0568c24dda5f1fe26e6ab955626cd2ee4e56ae0a7e1b78cbad84116454a96f116ce6a83241a3a4f4b38168efea45c8c80c67829260aab28805c8e57b2ab37fafa104be04568c36f4d56493aed0c96eb5c5635b67874c0bb406ed780d6eb20217388fbf38960064162fc429c9aaa43a7f87a4beba8e6e1a6d9d5c4e1a40223d7f917e9762594fb56e279c693b7aa6dc2bdebec8bcf47928850d3acaf551d6e2b39b522f77ddcc69f87622539fe37fd922f15a4530e8d36bfbf5d59245e411e223b8a1e2474bce6976c75c020a948fe857375c474a3e470fde2f90756f62cd25a094c320544be6448a2b50743b1b4f4a9e03160927a6a327427ff9da1cd7303fc041cadfed03a0cfe748b64a64041db58e3329ec76765b0282afb5fc93d19c6f72d4106bb32fd94747ba567381728b89456c7498eb8281c9f1c50df6b41574e4a375a1562233cc44eb6d5d36f8380a5a6fa035403c07bd5a5a1260ebb50225162310fd2c813931870f29beb9b8c161ec8ace4478979a34a53f1fa4be9674dcfea0b5747022d86e936ec045a4c4bd80c75fd3e60702d54222ac8d529ee9fdf8b0b0ca4fd2f987430a9741db0f2c0073656216fe0d3385801ac1e9721dc74cc1858e5c95b36c9f57a91578beef80";

    // Firefox version: 43.0.4 FP version: 11.2.202.559
    private String firefoxFlashPlayerC1 = "000006888000070276516e1353d7e9d3bdf8aed918f03ecb0b83a50b9ea618823fb76e86c29b8976a2188673150d438cfc9340d75011594687380a44429bd536ce2f9f10d718067d387f13e82ace4b2730849c4f3c94be7e42a55517aecf836a131b65cedba2d89b7cb83983cbf840f029aa8988d012daf2edd78146cab63d43a094fa65dea5a5aef719146b317c22af43cafa41a58963f3dad4edb3361ebc7bee2efc2f3367ffcd48f02d79e7dbb5e3025881c787f7cd2cf74ccbdc52ff44c4feea9a3786340020de94dd25f341f37e76726a3578426beaffc7dbe747b99995608e971461dcd6b2a695a90501c9cf8582317453075045441c0205ec46c4386ea946c23de5457147b8733809c9f25fb716c90b2696078c7450f5c576a3be763f4b2a2815f998f1d9a68a5b1a63ad544526fdd90d04df0ffd116fbf73dc054f67fcad5e95b42687d72be33c132c67ce79015c5ba134504d77ba2ced3c24d271468dda43a51ebdeb386c5d492b7c6b2e5e0fe135c77db709e988a8c31999442a387146c7718d71deb6bc0ddd61edbf9643511fbc0cd83fe94df3b9090e858642530f8d61e996e6208a6f7e1628547a9d3319c51a39b181f144667365a07c009b7710520875d5297372d1bf3a32fe5281cd4efd42a9b17c07cfa3b68056b8064412cd7d5fb733020233ab20d3ca8a9b40f6a3647aeabb8e8bd76044cb190d78fb52098dde1bcd9b6ff12c50cfa8a802505005832d95196fcba0a0c1ae478040766d9bb97a34fa1aab3cd8dcaf9fed674801041744112c9de3f0e4fca3eb82a38614d259748fe4688ecbd57896dbbd677714e194650224dd6b14d66a5458f292ae5e1e370e682715b6e7b6ffb4d9200d8ff58513acc585e37d15ad3302da406caafa8f791c6c29facb933cc0c3646f8675208c47510319cc0ba77071611ac9104ca0e2249db77e0232aa57dab2e6f99037487b0c1a26447aa682654ad255bf14eb16408e4882651b610e58edc9168459d91d1b5c900ff15515ad5c5249a22bdf2a1abe15dddbe53beab93762662edeee18d607e6848ad63d348b09abf5420081a2b912ac96adb786ac3304dbeddcdf56cc0751dd3e45ceaea115c33418160885d816d6a1e6f5fab248c6be60ad9cfb96e2ba4066fa5a880742bd922781e0f72e48ba53cbcb0e3aa2b322f38734dd43671861da95c57b3b41f10feccbfd4d57239577b90dfe6a464c2928d679a08b86f4adb87a088d8574763452cf5b6608ea37502d675d013895826b5697fc914de21d7d23c8b1ec752915b251a2bb1f52c3a9d161ebb6450c24a4afd113c9a690676602fe3bcab498d134735a096149b70cb78bfa153691f1edc65c23938ff6b77989e8392470897082f4168e98668455a31effd10d53fed0bd7b2d6c0fd24cf70e7783f635f4086c6a463aba6e63b16563334efafcb1569d5f77e337d6aee6900f2e9edd43fa98cd666342d3d9e5cc949eb37ed12d20909a5d1c5945bebd39ff7772a38d9b9bccf544eaef67a8a6f2fbd622c42b098e37876d8accc710806b4132c61632405a560adfc82cff1bd9cd0d4baf2bd18a054b503acb99ff396207d3c625ce912eeebf88cca68a19ff10b1c5c2341dc11df1155f018ce000a91c9a911c7e2a919c0a19d023de5107c40a64bb8529f4f457b471d439a6a73dbc1c9d8190ceca5aa3e1634445f82efdeceae3c305b848c921a71de58a7b887f78736e0ad51cb1b39d4c137bc6d037c4fb663facbf53376052bfb52441fd9e2e90f663370864ac0e7a9635d7d62ef0a09cf1e768ee9102fc31180ff193db4a29928ce5249010a36d6dee26d783d0dc8bccd472a8a7ef88d6626f7816d5ddb65e7ba266939bfe47343f5007792bf20308e0040a4e2696ae8f064326dba9ce18aed06b082735d5429275d34830b1df46366c2cf363ab6e05e94bd3a15c70251f3af3d2f4d85dd99bd3f26708c365b296ba85e3ba0cf9688b597636058012013b3686e092814f237d3b7f74a7a0024ffd66580825810df1106fe013f503c4677c35e377dc0ee97de324a11bd3c1bd829119bf86ca5b47b17ad268735ac59db02584ccbce702eebb464f10c2c6f921de68a43c83491b94d4201b7bbca037c6b70ecb3ed7737feb7484a9a9fa0d02dd6594238486c2371112e1f9";

    // FFMpeg version: 2.4.8 with libRTMP
    @SuppressWarnings("unused")
    private String ffmpegC1 = "366778720000000067458b6bc6237b3269983c647348336651dcb074ff5c49194a94e82aec585562291f8e23cd7ce846ba581b3dabd77e50f241b12efb1eb741e3a9e27946e145757c005f51c262d05b54082012f827b14d1b231602e8e9161fe7cd90118d43ef66760f0e145a2552332ef99c106372ed0d33c2dc7f9fd7ef1bc9c4a7419a07686b66fb6a4e325de4250d509b51b7d71b4331ba2d3f58e4837ca33071255ad9bb6225616c435d898c6205b13a3317a31d7258a84324e95a1d2d5e846367d4a8a275abbded08b28c8379cdd05343c6e0030b9b769a18b49ee4545424f3711186a82c0ec43608821d900274f8953a4186130821f57f1e3dbd3d7cdc8d7b7387f0ea6c701a2222e9dd16453ec80630a1d44f6141c29a41e1f87755fcad0b44672307053e820438015f46777ec62477972a485ceab96324dc4a885e6bd3ea519677512d8fd70b5838a43e155c5855382a4ea670ec42236ab07c482a3bd44e1dfb065a72329ad82cafcce4573c8d6d7a548f584bec892254181be96ddb7f43385ca4447602f9ff321a484a68fe78945743bb9a74fb40c23dfa26a01baadea1793ac3c675fb85e61229a5c670d1ed0e52e63f4a3705f04e4f3cc1f9237cb79b6494c75a2775653839d80ff11cbe15011861a85b23898c3947f9e94f355cafb515bb261274a8b6340d993c23100fb66a3f95405761b1570c7eeb35ae77f1e49b57b3500c31057ef85fef5d302ff70ba72500bfba1de984d04aa1ea481f3a828113e50ab75dca8f0f100b709065cb4a0115d07f5e5f48318a0947029d796447b906bd96c2421f128e16235dba1e1e3f1e66a89ec75d1c470a547beed37b64c5d951c5fd3e61142bf70b737b44115a3e9642c582030a5eb1f2084b23321a79d30f3b632feb683b81624970dfb66064eea5062406331411caff7f9e70271a0911ea71dc590f10aae0b77fd45beb06acd96d6ff21142091b5e880010212776afa8044c3b701617337ee114cde72232e30ede7450c5eb6848d6f62d47d4b74615c32a4a5c01ee39bb4ffc576f01c10c2284f1431901ef60ba24f3269b57017f7d30da49f5a555700b37b85fe11e80501aac88041c01b85f7f8fa76a23bd7276f85ac76f29705f6af8185e7da434355f1b82a1731377e67db5555c55ca2aa63f4ee7fc14e8d33d6a9812c97132f6da0938992953e0e8bf1f79ca92504d5c541d3deaad59341a8f28bc5d152a5f6e9f1d4e1b7e0977820851fac5a01ccb4b58536c285e4105fd587cac6ad82386d4e64521fe105c2bfa7f0eaa91593c1a59d84b556adf78a2aab739be8d0d2b70ec806cb5219e3773e369003b17272c04099b4c5cb7a76ad329f01d36ff75569450d13db312b03dafc90827e2ac255bf0fc5d17e4e3974f9e0a3b054f6bfd3432ff1559158d435649319e51fd4a6e2c82b5a1174e2ef74da9b54650088a885d702c082ad4afc65eb21be2198a85e075291aa65754c699534813ee209a0627440ae8370bbcf65721d51d4e700ef1d25718aeff0ba8473e0e44f0482eacfed0495b5aee4bf3b951558eabf6244c574c63d79de9242db6312a9bc24918099dff7d42437500e5f3e76906e86d2ac4f816183322df37af9db47acd829f75a34ee761844d7b597f9e810f2dd4c757ad672131d4641b6376e7b578476e4875de4c536e32de0d1a1c8c9665ec3d26464a8c0d26c4d3d473302e6f74f68ade6f202ec33f23e8c0498536d5146c850f23fb85aa6eb2ec063f0748593b0423aa6cf42f7c3fec3b41250b0b1817b9289357205e205dbaa8cc1186ab324dc3ac073f3ef6476b054ab45cf180cf16ec5d691cd9aecf3f6768850f33ccb111b7fb222e9946932950584877a3394974e3d2a04f142c1d6bd367b868d95d7f3f345ae02af74f79325e945454a0dfef4df2d5232110815b13a8274909f6f8cd0d05b1d75294638a2e0104e624bed96a2ab4c1aa0bbcacb23644859d77786eb24afaa2fa2149cf515469ef8161e600643e237e2114d05707711acd1550da794442699e9a1a6a255e477eb38d364c713b6a7e517b32511b461f25cfba29b3ab5b5d486bbf5184630f7e538b4b2b3a41e37294e46a11fbb29434313ab1009995426490161f63323e9725576fad0e44d8c96eeeea495c9bf44a06bc467c39";

    // FFMpeg version 3
    private String ffmpeg3C1 = "0000000009007c02f778551eceab8e1e362f07c5868a70b266d40220e5086118a22e3697d5fc2b6329c22ee7c7c9b0b5b345dfa8398fea3bc879208a1068425aa1174cb57258cfebe24da42ec4ff9da19c7b406f18c95bfdd04ee48b6f833214a47b8bf68eb05254e25ae9a9a8b496c5496f6e9cd7c68cb6bcc84169e67f3e372ffe186da8513862c5afdeb5a1218a860baccdd869b253dbab8a0efb59a48c442099875e55b74fd07a1b69af303e099873cb77e2d9e2c4741eb1f506a7f0b2962af91f5051c890a320994f4c8b36a01fa9c0f15f4b7cf7b6c3817da60918611d3b02ff7cc94038937537d3382e416bd57577834df9caf748352150ce92d0dbb40e871fc364e18b7e3569e39876c1ae0cb79ff6d7ab32f9f011869c8da4191ef46d3c1bc6b72d83812f2243d1752a412632d66a2075f5d302f45cdf2b98b802b79805dc4837d84eded1cc42cba7eec3137bf86dd0b2d7f624d521a3ff655bcf0df4101341d230e94651628e45a7abf8183101ce981e5e86a6faec5c79aa7ec7181ea459ed966e0c932a6f9e4bf554ab7b1c905cde054ed40d2fb89bbfc65752e58cfde4f2a04dc3ad1e5ef4fc64d57b071c14146a451f6a043156d2880f5957d78dfd3ff2b880f624ac70f27094f37734eaf1924f66ac3d648ae232930544a4420b65eed4696f7f50fe05fc948516650bfb9b65f953d848e3456965d0d76b627a84e35e9ddabd8b43f0fadaee458cdf59ebd773922e0ae507d5e64092e90ec67ed8439e8f221c753a9f9216f1cb85b5107bc7cc7c4fb2c3eed1afc709b192f50de69c1ffca5650efb523b5fab5997cac4b7ef5af2bf5973e6293bae1fc1da499be5515077de4ddb3f7fbcd741711c62b7d803b8d054cf578182714d1c445bd692ba1b839a09e7266fc85daa8fad433dabbd206cde1ea571770c6bfa7956c266ee8435eb01caa6f583c0d83c33eefcf3f97db917bdb299f8a89fb8860ab1cd644c1e82cce1323a9eb79d25f5dc570b70b40bb20061743353d0536ecdabd82f2277756228cb54fe6932c4c9bd130d2bfc700bf5d85da6946e96f82e0afd8e9c20cfeabf9a6a189bb17bbef539d1d9e73ee966fcb5d6bdbf98444de865be1605e34087b34cdf04e3aff55bd4137c522d2e3c7bf2634db326b22a474db7f8a0952ab3510de555396c18a378a8390b73bff7772fb2760fa5786a3b0459c45a1d1b0dff4cbbb65da0ed34f32116c3c2cc2b95825111bcfbb246c1505aa00c970d06d76cc950ce368b220db20487cba02bb1fc54b36567b42913d6b885062cd7795601d31a4a8e6d56467fa5c52162676fd526122513c3e47f2e6d9d6a19c4f8795431a94ced039221d27ac68a7a770e9305252271459b71d05505c4977b70e96851208bacdcdf7e682b3848840880f1fec2a32fb1406862bc83cad89adf729f3ba6ffd3fe808e8403e7c6a0f6dcda3211898f6f16505b078cc78b80db600ded2f200f25ff5dcb31d32cbe2056b8bdc31091631abbd2ac28d61fe32480ec5035bbf35f173393f0d59a21d2300b2db4b59eaa63ffe0f93d65dc028516905f22a9c7dff387c4eb78c6300e7ebbb2a6663cfbe5a9977caf5764ba3f0aba57db4e6051e0aec6e05b1bdaa649a97875edbc40bcc4214bb7fef72730d4368acbd3e10ab4c7642a1c53f93ca26db9c766555be26fdff0e2a721425b95762b0b776fd3e3eb17313d53129c9f34003c99a5196a8c6aacb6d64b51dd98b27c5b59a70bd941a1ba0300fb744cfc499d997e6d93392d28a767d116dfbc2cb9eb40540d11eb304ad9322386fdc597d55d3d1c67bc54df35479afb19ea2cd204dd331efb49c1e172f6bb89fbf58207dcae6c12f8a7598bef1e872c0c35f22b4c29df10f9bb1add3aeb887ab5fb38c036b88346f761c42f295dfe0b437dfb4f30a71276ab7d409ce424963f9ebb4f1f47e52b18786f279b5a87226f66981dd6d0ad11f49af31055ff69710fda3457c4b7852db2e3936d0778bf879f1734ad3a24ffb1ae9584760b9cb0452a53ba0962ffe2f605af52830bd211a5488894cc0b052255048711cd198510a9e943bf8b839198455fbd41073005d303990b88d9b63656d43cfec8ed83748f4b0f0fc5120216794b22a054e5bc58abd8c41096070884393453ce509694afbeabe0";

    private String ffmpeg3ServerHS = "0000009b0400000131e2c76af9b24f8e655e69195eb69ee2ae0733f3e47b111135f34a38d88ecdf02412501593e8d0907e2c133e95d15c195c0b4ed3f8e1b238e558566501fe1a8582190717a15f11ce1461eb731eacc3063cd8625e3d4dd3e8f2183dbea7d78387feae5c485ec41e1e14f0c2ad7839d5f99cb8e82e809e4e0372b56fb82c536bb9201087ea49a320765d629c380711731ddd96fe3812d7a11ab020e0321626109bc487b536a5adb2434500a6e094861127f56bafaa48255548ab0f648e8fa4883199449d25fa0d93aa15cc5ebacb528522eefde5cdfc00ebfc941c1f50e671f256a27f5a2292cc1675884c70bcbccbad4d65eea9940a58e6ee54b91e790cc1b7b1b9f2df00fd4316645027079e5badfdcc0c198031d4cab9ae03f2913918250c6a079f6e9d8da543af918bb45c00722460284208bfbff8b18d72ff4649c4f102b78ee3cfffdb8d1df3646c2264e83a5a2413153283b4dc2c76a2a958faed2be1669fe34bba40a9ef7d568b2491b24810e0bb72a3cb9f39b00b4b7a350e160a8d98643d96815d6893e9e652f030e203084a21102abaf416b1e956bf6a70e6043df657102ba894c4e2330a8185ac6195123cdc645299254d1e20635ddd3da577c8e3c953d63c8c140f14aba191c7fb0efbc793e97d7a2d22001d44725aeac3d03b3d5c7d5f09b0f04d1f24595839df235db386a612f008f6be0a80c7a9e95252927489779393b1278e6bc77e6aed23716af506129eaf38ffe24ad1148cfc8762b7171ce8269000c326c3580736693e56e06ff93e05c942175f1da6124f253d6c310ab2c62aec551bb22890bfb8aa8c5db042575b1ce92f97b8b4a25ce152714f180842430daf2bfd865e9bd6df53ce341dc8af7e3fcf0c449eef1b249004c761395a91fc607c41dd81619a385e1da7354e7da3c8c6b66a4bc0b09cb2d88304ff8afdbc86e4691a1d104b22b9035b207e9bd967cf6ff6de4ffb5563bcf7d491a951e2b24184337e5fafdabc0ddfd22499145d465e168182852820e482202affba9851b87f5094f7ec52fb6398b44705b703ed1d84564472de247544c785787da4e0f9e3fba1b0552861f5090294def5756febdef1f19a5047e1c5de8c9ec964bf249ea1a0e60a2a133ba212a596f68e3add540fa9122ac1f38d8ac2ab5006acad9ae4319ed0714fe27fe2d6c710214b18d69548fac489eeba32373133eb44744acd4c26d1e1b35da27ecc23a7bfa70c4af0b7b9d3420a04844643229c1a167e2af5e6e8788da935ec8784ba50be75963a5ffca15feac0b4c2175fc1cb40e8b0acb000323e6a5479a91175ce4d4b47111d4cf8d8f2f9c8b40318baeae5da5b949a7d9d95892e6710e9dea8881da9bb881579cd464b6b4d3b895ccdb2efde4ed858963bf4297187a6c39d5d857fa3436cf1f027691e888289f7e575df413b78cfa71418064b68ff2950ad81568e83ee1270c53fd4f7112f5d11814a6bcbe1a7eff37bdfd8bb20634da8fdb3fea97cc6301b1e3caa6717c67d8452010524092351360d7a4885e7e109fd3b897832dcc464af19fb2781d05b32c65f698f6e8d1f3465866d8bd0df9918bbbc4d2f128665ad14447017a91cf1c9ffa6f4a10b9b147b742f20a71dff487abfd53784188888f0e6a2815db47baa130b980f051c0d722ae65623fac9e923f09b9a024c24ce9f748e9a896e569c88e57e008b39d32953ecea9f8db1e6697083eb2c354f1e411e66e82d6ad3dfd92199cbbc2df099a6ec9012c457525f7abefdb612efcac3dabc567f5a6cab4c43ad8c67c01b91001ec9888caaa0337020406ee944eeee407871b85393fc26a305699e0b4720b681616268f8f6b7006d8dcda9d4e7a75f9b75e265349cb30ba25307b5ff4011af1451496dc671d039d74298c4c5dfb8b5ec5bb917a7470133bf24563a774c72fd8f22d9f427ec45b1c4326d9ca9b3a7fe6c2c76f060d93c0b7665c79afdcd35d7db4266fe8f8174109616ab4ec16fed3a4d124cf44e0fd516e672d8c6f2f9faa8f43af4b2c3583952fd75bfbf667a7fde61851f8a2e13d869c1fb98b317e16f8ebd42c1db7a46574654181216e18ca0529d27f5ffb5d6112e8ba0badb2ad28edda26b616690f3231143dac33c494293735ae7ff034d9c8d2126eda3bb94a246";

    private String ffmpeg3C2 = "0000009b0400000131e2c76af9b24f8e655e69195eb69ee2ae0733f3e47b111135f34a38d88ecdf02412501593e8d0907e2c133e95d15c195c0b4ed3f8e1b238e558566501fe1a8582190717a15f11ce1461eb731eacc3063cd8625e3d4dd3e8f2183dbea7d78387feae5c485ec41e1e14f0c2ad7839d5f99cb8e82e809e4e0372b56fb82c536bb9201087ea49a320765d629c380711731ddd96fe3812d7a11ab020e0321626109bc487b536a5adb2434500a6e094861127f56bafaa48255548ab0f648e8fa4883199449d25fa0d93aa15cc5ebacb528522eefde5cdfc00ebfc941c1f50e671f256a27f5a2292cc1675884c70bcbccbad4d65eea9940a58e6ee54b91e790cc1b7b1b9f2df00fd4316645027079e5badfdcc0c198031d4cab9ae03f2913918250c6a079f6e9d8da543af918bb45c00722460284208bfbff8b18d72ff4649c4f102b78ee3cfffdb8d1df3646c2264e83a5a2413153283b4dc2c76a2a958faed2be1669fe34bba40a9ef7d568b2491b24810e0bb72a3cb9f39b00b4b7a350e160a8d98643d96815d6893e9e652f030e203084a21102abaf416b1e956bf6a70e6043df657102ba894c4e2330a8185ac6195123cdc645299254d1e20635ddd3da577c8e3c953d63c8c140f14aba191c7fb0efbc793e97d7a2d22001d44725aeac3d03b3d5c7d5f09b0f04d1f24595839df235db386a612f008f6be0a80c7a9e95252927489779393b1278e6bc77e6aed23716af506129eaf38ffe24ad1148cfc8762b7171ce8269000c326c3580736693e56e06ff93e05c942175f1da6124f253d6c310ab2c62aec551bb22890bfb8aa8c5db042575b1ce92f97b8b4a25ce152714f180842430daf2bfd865e9bd6df53ce341dc8af7e3fcf0c449eef1b249004c761395a91fc607c41dd81619a385e1da7354e7da3c8c6b66a4bc0b09cb2d88304ff8afdbc86e4691a1d104b22b9035b207e9bd967cf6ff6de4ffb5563bcf7d491a951e2b24184337e5fafdabc0ddfd22499145d465e168182852820e482202affba9851b87f5094f7ec52fb6398b44705b703ed1d84564472de247544c785787da4e0f9e3fba1b0552861f5090294def5756febdef1f19a5047e1c5de8c9ec964bf249ea1a0bb05e73f2b2743992b6cadef5441bd750821a763f217ca0f5535d68e1e60121bd0714fe27fe2d6c710214b18d69548fac489eeba32373133eb44744acd4c26d1e1b35da27ecc23a7bfa70c4af0b7b9d3420a04844643229c1a167e2af5e6e8788da935ec8784ba50be75963a5ffca15feac0b4c2175fc1cb40e8b0acb000323e6a5479a91175ce4d4b47111d4cf8d8f2f9c8b40318baeae5da5b949a7d9d95892e6710e9dea8881da9bb881579cd464b6b4d3b895ccdb2efde4ed858963bf4297187a6c39d5d857fa3436cf1f027691e888289f7e575df413b78cfa71418064b68ff2950ad81568e83ee1270c53fd4f7112f5d11814a6bcbe1a7eff37bdfd8bb20634da8fdb3fea97cc6301b1e3caa6717c67d8452010524092351360d7a4885e7e109fd3b897832dcc464af19fb2781d05b32c65f698f6e8d1f3465866d8bd0df9918bbbc4d2f128665ad14447017a91cf1c9ffa6f4a10b9b147b742f20a71dff487abfd53784188888f0e6a2815db47baa130b980f051c0d722ae65623fac9e923f09b9a024c24ce9f748e9a896e569c88e57e008b39d32953ecea9f8db1e6697083eb2c354f1e411e66e82d6ad3dfd92199cbbc2df099a6ec9012c457525f7abefdb612efcac3dabc567f5a6cab4c43ad8c67c01b91001ec9888caaa0337020406ee944eeee407871b85393fc26a305699e0b4720b681616268f8f6b7006d8dcda9d4e7a75f9b75e265349cb30ba25307b5ff4011af1451496dc671d039d74298c4c5dfb8b5ec5bb917a7470133bf24563a774c72fd8f22d9f427ec45b1c4326d9ca9b3a7fe6c2c76f060d93c0b7665c79afdcd35d7db4266fe8f8174109616ab4ec16fed3a4d124cf44e0fd516e672d8c6f2f9faa8f43af4b2c3583952fd75bfbf667a7fde61851f8a2e13d869c1fb98b317e16f8ebd42c1db7a46574654181216e18ca0529d27f5ffb5d6112e8ba0badb2ad28edda26b616690f3231143dac33c494293735ae7ff034d9c8d2126eda3bb94a246";

    @Before
    public void setUp() throws Exception {
    }

    @After
    public void tearDown() throws Exception {
    }

    @Test
    public void testServerDigest() throws InterruptedException {
        log.info("\n testServerDigest");
        IoBuffer r5c1 = IoBuffer.allocate(1536);
        r5c1.put(IOUtils.hexStringToByteArray(red5clientC1));
        r5c1.flip();
        InboundHandshake in = new InboundHandshake();
        in.decodeClientRequest1(r5c1);
        int algorithm = 0;
        byte[] handshakeBytes = in.getHandshakeBytes();
        // get the handshake digest
        int digestPos = in.getDigestOffset(algorithm, handshakeBytes, 0);
        log.debug("Digest position offset: {}", digestPos);
        in.calculateDigest(digestPos, handshakeBytes, 0, RTMPHandshake.GENUINE_FMS_KEY, 36, handshakeBytes, digestPos);
        log.debug("Calculated digest: {}", Hex.encodeHexString(Arrays.copyOfRange(handshakeBytes, digestPos, digestPos + 32)));
        Assert.assertTrue(in.verifyDigest(digestPos, handshakeBytes, RTMPHandshake.GENUINE_FMS_KEY, 36));
    }

    /** Serverside test */
    @Test
    public void testInboundHandshake() {
        log.info("\n testInboundHandshake");
        IoBuffer r5c1 = IoBuffer.allocate(1536);
        r5c1.put(IOUtils.hexStringToByteArray(red5clientC1));
        r5c1.flip();
        InboundHandshake in = new InboundHandshake();
        // try old method
        IoBuffer S1 = in.doHandshake(r5c1);
        log.debug("S1: {}", Hex.encodeHexString(S1.array()));
        Assert.assertNotNull(S1);
    }

    @Test
    public void testValidateFromBrowsers() {
        log.info("\n testValidateFromBrowsers");
        // no handshake type bytes are included here, the C0
        // server side handshake handler
        InboundHandshake in = new InboundHandshake();
        // CHROME
        IoBuffer cc = IoBuffer.allocate(1536);
        cc.put(IOUtils.hexStringToByteArray(chromeFlashPlayerC1));
        cc.flip();
        log.debug("Validate chrome: {}", cc);
        boolean chrome = in.validate(cc.array());
        cc.clear();
        // FIREFOX
        cc.put(IOUtils.hexStringToByteArray(firefoxFlashPlayerC1));
        cc.flip();
        log.debug("Validate firefox: {}", cc);
        boolean firefox = in.validate(cc.array());
        cc.clear();
        Assert.assertTrue(chrome && firefox);
    }

    @Test
    public void testValidateFromFFMpeg() {
        log.info("\n testValidateFromFFMpeg");
        // server side handshake handler
        InboundHandshake in = new InboundHandshake((byte) 0x03);
        // set whether or not unverified will be allowed
        in.setUnvalidatedConnectionAllowed(true);
        // FFMPEG
        IoBuffer cc = IoBuffer.allocate(1536);
        // C1
        //cc.put(IOUtils.hexStringToByteArray(ffmpegC1));
        cc.put(IOUtils.hexStringToByteArray(ffmpeg3C1));
        cc.flip();
        log.debug("FFMpeg c1: {}", cc);
        // use pregenerated server handshake bytes
        IoBuffer svr = IoBuffer.wrap(IOUtils.hexStringToByteArray(ffmpeg3ServerHS));
        in.setHandshakeBytes(svr.array());
        // decode C1
        IoBuffer s0s1s2 = in.decodeClientRequest1(cc);
        log.debug("Server reponse: {}", s0s1s2);
        // C2
        IoBuffer c2 = IoBuffer.wrap(IOUtils.hexStringToByteArray(ffmpeg3C2));
        // decode C2
        boolean ffmpeg = in.decodeClientRequest2(c2);
        // prevent travis CI from failing; we know this is broken atm
        if (!ffmpeg) {
            log.warn("FFMpeg validation failed");
        }
        //Assert.assertTrue(ffmpeg);
    }

    @Test
    public void testOutgoingPublicKey() {
        log.info("\n testOutgoingPublicKey");
        byte[] sharedSecret = IOUtils.hexStringToByteArray("04cde275ff2d72113cfac0e914bf4dab3bc747dfb63c23314b470181e7260a1f37ae3ef259f3bd3fe80ec5ebf99d501e4cce69d224268e6d5304cbfb94bc71d59f15564f96a089f9a93b5e08d9ea0c45ca5934ff2c9729cc73856fd130cb6bfe29f14a0ec36e0eee0cd5c21c1d08f6f9979adc162d24831318a3b9145d835222");
        byte[] outgoingPublicKey = IOUtils.hexStringToByteArray("d5055cd576014c41fc91811a7f6aaaacc8f5bef9383cabb3c91afd392448255ef14a38e8c985197652a47a31e2852d7923dae7c2c10df3b325556c4f2fbc14b04e244570c42526e67d2c5c3e75fcd1732c7b915839653274df15d887c10852dae81d54e52fe26946fd7936fc69926e7a33c9e3aba7ae2a93cbbd4c481cde3f90");
        InboundHandshake in = new InboundHandshake();
        byte[] rc4keyOut = new byte[32];
        in.calculateHMAC_SHA256(outgoingPublicKey, 0, outgoingPublicKey.length, sharedSecret, RTMPHandshake.KEY_LENGTH, rc4keyOut, 0);
        log.debug("rc4keyOut: {}", rc4keyOut);
        Assert.assertNotEquals(IOUtils.hexStringToByteArray("0000000000000000000000000000000000000000000000000000000000000000"), rc4keyOut);
    }

    @SuppressWarnings("unused")
    @Test
    public void testDumpedData() throws InterruptedException {
        log.info("\n testDumpedData");
        // client side
        byte[] clientSwfHash = IOUtils.hexStringToByteArray("f4d190ed4afa3bdbb001ded30f4ab005f4c30c79730373510694f6f0ed86b59f");
        int clientSwfSize = 90559;
        byte[] initialClientDigest = IOUtils.hexStringToByteArray("ed cf cf d3 70 7a 19 99 84 e7 21 2b 81 0c 92 c115 4d 63 51 0c 46 a6 72 2b a6 4f 9a 5d d6 6c 5b");
        byte[] calculatedDigestKeyFromSecureKeyAndServerDigest = IOUtils.hexStringToByteArray("01 6c 9f a8 6c 82 02 d8 ba fb 18 14 74 0c fd c8ed ff 9c 19 44 a5 ad e8 65 64 69 f7 54 a1 4d 97");
        byte[] clientSignatureCalculated = IOUtils.hexStringToByteArray("b0 b0 f3 97 ef 99 7c 74 2e bd cb 86 2f 12 79 7358 3e 9d c9 62 7f c9 82 5d df 7a 52 58 23 a8 4d");
        byte[] serverSentSignature = IOUtils.hexStringToByteArray("93 5b f5 23 cc 7d 23 26 7f 6a d3 d1 df d5 cd a1eb 7a 09 12 ba b6 cd 8e cb 57 01 53 2d 00 31 0b");
        byte[] digestKey = IOUtils.hexStringToByteArray("08 ac e1 4e 20 b1 6f 31 4a 93 b7 5f 1b b6 42 ef9b 2d e6 c9 b3 c5 35 ed 09 72 d9 90 31 60 3e b6");
        byte[] signatureCalculated = IOUtils.hexStringToByteArray("93 5b f5 23 cc 7d 23 26 7f 6a d3 d1 df d5 cd a1eb 7a 09 12 ba b6 cd 8e cb 57 01 53 2d 00 31 0b");
        // server side
        byte[] clientRequest1 = IOUtils.hexStringToByteArray(
                "25f1a5080a002d0267458b6bc6237b3269983c647348336651dcb074ff5c49194a94e82aec585562291f8e23cd7ce846ba581b3dabd77e50f241b12efb1eb741e3a9e27946e145757c005f51c262d05b54082012f827b14d1b231602e8e9161fe7cd90118d43ef66760f0e145a2552332ef99c106372ed0d33c2dc7f9fd7ef1bc9c4a7419a07686b66fb6a4e325de4250d509b51b7d71b4331ba2d3f58e4837ca33071255ad9bb6225616c435d898c6205b13a3317a31d7258a84324e95a1d2d5e846367d4a8a275abbded08b28c8379cdd05343c6e0030b9b769a18b49ee4545424f3711186a82c0ec43608821d900274f8953a4186130821f57f1e3dbd3d7cdc8d7b7387f0ea6c701a2222e9dd16453ec80630a1d44f6141c29a41e1f87755fcad0b44672307053e820438015f46777ec62477972a485ceab96324dc4a885e6bd3ea519677512d8fd70b5838a43e155c5855382a4ea670ec42236ab07c482a3bd44e1dfb065a72329ad82cafcce4573c8d6d7a548f584bec892254181be96ddb7f43385ca4447602f9ff321a484a68fe78945743bb9a74fb40c23dfa26a01baadea1793ac3c675fb85e61229a5edcfcfd3707a199984e7212b810c92c1154d63510c46a6722ba64f9a5dd66c5bf11cbe15011861a85b23898c3947f9e94f355cafb515bb261274a8b6340d993c23100fb66a3f95405761b1570c7eeb35ae77f1e49b57b3500c31057ef85fef5d302ff70ba72500bfba1de984d04aa1ea481f3a828113e50ab75dca8f0f100b709065cb4a0115d07f5e5f48318a0947029d796447b906bd96c2421f128e16235dba1e1e3f1e66a89ec75d1c470a547beed37b64c5d951c5fd3e61142bf70b737b44115a3e9642c582030a5eb1f2084b23321a79d30f3b632feb683b81624970dfb66064eea5062406331411caff7f9e70271a0911ea71dc590f10aae0b77fd45beb06acd96d6ff21142091b5e880010212776afa8044c3b701617337ee114cde72232e30ede7450c5eb6848d6f62d47d4b74615c32a4a5c01ee39bb4ffc576f01c10c2284f1431901ef60ba24f3269b57017f7d30da49f5a555700b37b85fe11e80501aac88041c01b85f7f8fa76a23bd7276f85ac76f29705f6af8185e7da434355f1b82a1731377e67db5555c55ca2aa63f4ee7fc14e8d33d6a9812c97132f6da0938992953e0e8bf1f79ca92504d5c541d3deaad59341a8f28bc5d152a5f6e9f1d4e1b7e0977820851fac5a01ccb4b58536c285e4105fd587cac6ad82386d4e64521fe105c2bfa7f0eaa91593c1a59d84b556adf78a2aab739be8d0d2b70ec806cb5219e3773e369003b17272c04099b4c5cb7a76ad329f01d36ff75569450d13db312b03dafc90827e2ac255bf0fc5d17e4e3974f9e0a3b054f6bfd3432ff1559158d435649319e51fd4a6e2c82b5a1174e2ef74da9b54650088a885d702c082ad4afc65eb21be2198a85e075291aa65754c699534813ee209a0627440ae8370bbcf65721d51d4e700ef1d25718aeff0ba8473e0e44f0482eacfed0495b5aee4bf3b951558eabf6244c574c63d79de9242db6312a9bc24918099dff7d42437500e5f3e76906e86d2ac4f816183322df37af9db47acd829f75a34ee761844d7b597f9e810f2dd4c757ad672131d4641b6376e7b578476e4875de4c536e32de0d1a1c8c9665ec3d26464a8c0d26c4d3d473302e6f74f68ade6f202ec33f23e8c0498536d5146c850f23fb85aa6eb2ec063f0748593b0423aa6cf42f7c3fec3b41250b0b1817b9289357205e205dbaa8cc1186ab324dc3ac073f3ef6476b054ab45cf180cf16ec5d691cd9aecf3f6768850f33ccb111b7fb222e9946932950584877a3394974e3d2a04f142c1d6bd367b868d95d7f3f345ae02af74f79325e945454a0dfef4df2d5232110815b13a8274909f6f8cd0d05b1d75294638a2e0104e624bed96a2ab4c1aa0bbcacb23644859d77786eb24afaa2fa2149cf515469ef8161e600643e237e2114d05707711acd1550da794442699e9a1a6a255e477eb38d364c713b6a7e517b32511b461f25cfba29b3ab5b5d486bbf5184630f7e538b4b2b3a41e37294e46a11fbb29434313ab1009995426490161f63323e9725576fad0e44d8c96eeeea495c9bf44a06bc467c39");
        // prior to any manipulation minus the last 32 bytes
        byte[] serverInitialHandshake = IOUtils.hexStringToByteArray(
                "00000010040000013bb703ae42f0a30b53ab81fa65005d707b7740b0dbe119d134c64b0a4b7a84c9709ecc315dabc88affb144efdf4ece9369fa0dfc97ebc65bdd984ef65a3e292e10e29a9b2032e3114b2d1f602e931590f0f577a78afc7bd44131a3645a04a0f337f6b105d8d2bd9897cbf051ffe8772bbcb098692c3742b6a44f3fb13391fc40424bac58c45c819f285ba13a15d886b7fe6d9eec27da33326b6ea8342143f1b85612ddce9acae01ba562e765c542b2fcebf93415d912a8f1a062bb5d346801c4d911768303bd1f4f9c4919d679a4b9333d46e493d59f1642728c97b3cee43765f789e39b1ac57d97fb10cb812da0960bb016f6836243dd1bb1978faa272a9ca71e291036f27ed056979b3a65f5deba3386bdb4fbfa0551b056cc41f1cc7cdf7f8210832a0d7c56b7a886ccfc753ebb4304e8145acd48c58ef9ee399d1c8bfc329ae889dde5fd0c6f4b921b64674fc222f37a1cce3ca872fb59b1be68ca53c3efa105e1ce668012a1ff995c83192464421f7f980c5eae67c6d8b2ac055e5c8b4e169c8b537214e0a3281d196bec6ead19db1df4687e55edcbfc108163b43786d73c05cee95c8a63c29057823cd6f0874e7cad428e9a5a094bf087f5a87b5c6c859b137ce36d6c4405e1e9dec2e137cee4d9cdcee5e65f086a0023d08bb64b7e777d770db763c9ddb02c12d530aaada89bd294c05047df3994217dc8a33f0714ab72332a24edceb66b6574934ae3f50d5bc6caac3ad9ec47d76691a44f3fd834a2cc20a17c303484f0e4721aa4d19c80341b371f336ac9d4098c1befc5b865f1512484face4574fcc5e5162881ce156403b9fc712cfc4448506f88e9f4fb1a7a90d3b11864b8d808b609898852c809832a8f06843647f1f68793769be732de2b807ac298fd0b541da5d319cdc2c0f5a0450d84200c34240d3c9cc62180d7160d227a57e586312cc1f92cdaabc24d1931e7a975291263cae70ad63b9f793b04754f7d6f2d77137592fbe0c3890b16b58ca35a49d777a609c5cdaeac70ce0064d7d7eb4ab1f60e5b4e701f72f11c5b9942bc20879fff84b3f66736c386d19f68ee64766017f6733da844fc69f8cf6d045ab77a8d9498b78700dea54d0784e4ffbc148f61bbca0699f93df1f2d43932d1a20fee5d5058ae464c38d51e98f26d69236eee4e298f80c214895a7f26bda2f56cf48579bc950c945dc0c47ef5af2e1d00720e9d367b0e71c479f5a4b2d74c5141c04bcd48dd27a9af5a132bb6d4ef011710ae686ba3cf18c2f568a59870d2cb7567d3ae633789d7a61a8ae75e46ccf3383451bbad1359152dd5bb863d2968c3b6a0b2e844c273db3fb1929e276ab49a65f5bfdf600534a4ccf9dabb727959cbdff200d75df055fe6602b91114d2ec90e12d82c66a055dafa6fd145d992d82babd24da9300d557092100321a42e589d97ae86a7ad13ac567b5209ff0c333d50994c4e23310c9d407f0e552798ffef9a6759c09a5e2a2d0afc58c11c31883d8827f9ebd12cc59d9d91d121439f9fe7b71db2638231704d755c81de8cff64e233e1002942f08e4ab96d605969c56dba25d26cd90a9248da61e209c043a9fa5e6f1b4a8949964763ebf3c889aa0513fd6f2825af278ef91fe9fb6bd285465106090d1e76eec5595be40e183060cbff9d221229600234e9c14ebe1517c39a7c969369b9ad7dc10440a4749f96410b888279b55e6a15ed112100765b30ef610a3e96913d909d5aa4534df6b4c637a17c6b87570f181ec010c903b15fb4c3fecc45ea8bc2241a101e2026a514654723c673c25a6728e8bc680222bee748bbc7e055b45e21c3f2c3c9a70de3e5fadcc34bdc0c061467cfde3f4cdbd9e8884dfb8b503998d65c94bf6202cdc16498aeab0e42299cfe6421bc9d889dbd89dd14cb766927e8245289d699a198eda3c272a635f971f932e9bf9185cc2f6ed8374d4d859d522dd3ca87125a1cd5c744167e7ace2251ce33c861da524d966828b51fa6a03ac222b7f1dcb27cc1271367d500cbb012a3486c3c7c0c961a39261d0b00e2f468ff919755968697b6806b7a68d31743bd3c069dc64265e00e8eabc28bcd639d406c01443b7e12e80828df4d4eb4edadea6f5ccc128ffaa57e5b45fce06c5ef6f303a6b2593d5b0cec59eeb41");
        byte[] initialServerDigest = IOUtils.hexStringToByteArray("5dbcf41ee9847bc4c27f8a6008e3b4f5aa2daad89c982ceeac5f7ec1d768a8ba");
        byte[] calculatedClientDigest = IOUtils.hexStringToByteArray("edcfcfd3707a199984e7212b810c92c1154d63510c46a6722ba64f9a5dd66c5b");
        Assert.assertArrayEquals(initialClientDigest, calculatedClientDigest);
        byte[] calculatedDigestKey = IOUtils.hexStringToByteArray("08ace14e20b16f314a93b75f1bb642ef9b2de6c9b3c535ed0972d99031603eb6");
        Assert.assertArrayEquals(digestKey, calculatedDigestKey);
        byte[] signatureResponse = IOUtils.hexStringToByteArray("935bf523cc7d23267f6ad3d1dfd5cda1eb7a0912bab6cd8ecb5701532d00310b");
        Assert.assertArrayEquals(serverSentSignature, signatureResponse);

        byte[] s0s1s2 = IOUtils.hexStringToByteArray(
                "0300000010040000013bb703ae42f0a30b53ab81fa65005d707b7740b0dbe119d134c64b0a4b7a84c9709ecc315dabc88affb144efdf4ece9369fa0dfc97ebc65bdd984ef65a3e292e10e29a9b2032e3114b2d1f602e931590f0f577a78afc7bd44131a3645a04a0f337f6b105d8d2bd9897cbf051ffe8772bbcb098692c3742b6a44f3fb13391fc40424bac58c45c819f285ba13a15d886b7fe6d9eec27da33326b6ea8342143f1b85612ddce9acae01ba562e765c542b2fcebf93415d912a8f1a062bb5d346801c4d911768303bd1f4f9c4919d679a4b9333d46e493d59f1642728c97b3cee43765f789e39b1ac57d97fb10cb812da0960bb016f6836243dd1bb1978faa272a9ca71e291036f27ed056979b3a65f5deba3386bdb4fbfa0551b056cc41f1cc7cdf7f8210832a0d7c56b7a886ccfc753ebb4304e8145acd48c58ef9ee399d1c8bfc329ae889dde5fd0c6f4b921b64674fc222f37a1cce3ca872fb59b1be68ca53c3efa105e1ce668012a1ff995c83192464421f7f980c5eae67c6d8b2ac055e5c8b4e169c8b537214e0a3281d196bec6ead19db1df4687e55edcbfc108163b43786d73c05cee95c8a63c29057823cd6f0874e7cad428e9a5a094bf087f5a87b5c6c859b137ce36d6c4405e1e9dec2e137cee4d9cdcee5e65f086a0023d08bb64b7e777d770db763c9ddb02c12d530aaada89bd294c05047df3994217dc8a33f0714ab72332a24edceb66b6574934ae3f50d5bc6caac3ad9ec47d76691a44f3fd834a2cc20a17c303484f0e4721aa4d19c80341b371f336ac9d4098c1befc5b865f1512484face4574fcc5e5162881ce156403b9fc712cfc4448506f88e9f4fb1a7a90d3b11864b8d808b609898852c809832a8f06843647f1f68793769be732de2b807ac298fd0b541da5d319cdc2c0f5a0450d84200c34240d3c9cc62180d7160d227a57e586312cc1f92cdaabc24d1931e7a975291263cae70ad63b9f793b04754f7d6f2d77137592fbe0c3890b16b58ca35a49d777a609c5cdaeac70ce0064d7d7eb4ab1f60e5b4e701f72f11c5b9942bc20879fff84b3f66736c386d19f68ee64766017f6733da844fc69f8cf6d045ab77a8d9498b78700dea54d0784e4ffbc148f61bbca0699f93df1f2d43932d1a20fee5d5058ae464c38d51e98f26d69236eee4e298f80c214895a7f26bda2f56cf48579bc950c945dc0c47ef5af2e1d00720e9d367b0e71c479f5a4b2d74c5141c04bcd48dd27a9af5a132bb6d4ef011710ae686ba3cf18c2f568a59870d2cb7567d3ae633789d7a61a8ae75e46ccf3383451bbad1359152dd5bb863d2968c3b6a0b2e844c273db3fb1929e276ab49a65f5bfdf600534a4ccf9dabb727959cbdff200d75df055fe6602b91114d2ec90e12d82c66a055dafa6fd145d992d82babd24da9300d557092100321a42e589d97ae86a7ad13ac567b5209ff0c333d50994c4e23310c9d407f0e552798ffef9a6759c09a5e2a2d0afc58c11c31883d8827f9ebd12cc59d9d91d121439f9fe7b71db2638231704d755c81de8cff64e233e1002942f08e4ab96d605969c56dba25d26cd90a9248da61e209c043a9fa5e6f1b4a8949964763ebf3c889aa0513fd6f2825af278ef91fe9fb6bd285465106090d1e76eec5595be40e183060cbff9d221229600234e9c14ebe1517c39a7c969369b9ad7dc10440a4749f96410b888279b55e6a15ed112100765b30ef610a3e96913d909d5aa4534df6b4c637a17c6b87570f181ec010c903b15fb4c3fecc45ea8bc2241a101e2026a514654723c673c25a6728e8bc680222bee748bbc7e055b45e21c3f2c3c9a70de3e5fadcc34bdc0c061467cfde3f4cdbd9e8884dfb8b503998d65c94bf6202cdc16498aeab0e42299cfe6421bc9d889dbd89dd14cb766927e8245289d699a198eda3c275dbcf41ee9847bc4c27f8a6008e3b4f5aa2daad89c982ceeac5f7ec1d768a8bae7ace2251ce33c861da524d966828b51fa6a03ac222b7f1dcb27cc1271367d500cbb012a3486c3c7c0c961a39261d0b00e2f468ff919755968697b6806b7a68d31743bd3c069dc64265e00e8eabc28bcd639d406c01443b7e12e80828df4d4eb4edadea6f5ccc128ffaa57e5b45fce06c5ef6f303a6b2593d5b0cec59eeb4125f1a5080a002d0267458b6bc6237b3269983c647348336651dcb074ff5c49194a94e82aec585562291f8e23cd7ce846ba581b3dabd77e50f241b12efb1eb741e3a9e27946e145757c005f51c262d05b54082012f827b14d1b231602e8e9161fe7cd90118d43ef66760f0e145a2552332ef99c106372ed0d33c2dc7f9fd7ef1bc9c4a7419a07686b66fb6a4e325de4250d509b51b7d71b4331ba2d3f58e4837ca33071255ad9bb6225616c435d898c6205b13a3317a31d7258a84324e95a1d2d5e846367d4a8a275abbded08b28c8379cdd05343c6e0030b9b769a18b49ee4545424f3711186a82c0ec43608821d900274f8953a4186130821f57f1e3dbd3d7cdc8d7b7387f0ea6c701a2222e9dd16453ec80630a1d44f6141c29a41e1f87755fcad0b44672307053e820438015f46777ec62477972a485ceab96324dc4a885e6bd3ea519677512d8fd70b5838a43e155c5855382a4ea670ec42236ab07c482a3bd44e1dfb065a72329ad82cafcce4573c8d6d7a548f584bec892254181be96ddb7f43385ca4447602f9ff321a484a68fe78945743bb9a74fb40c23dfa26a01baadea1793ac3c675fb85e61229a5edcfcfd3707a199984e7212b810c92c1154d63510c46a6722ba64f9a5dd66c5bf11cbe15011861a85b23898c3947f9e94f355cafb515bb261274a8b6340d993c23100fb66a3f95405761b1570c7eeb35ae77f1e49b57b3500c31057ef85fef5d302ff70ba72500bfba1de984d04aa1ea481f3a828113e50ab75dca8f0f100b709065cb4a0115d07f5e5f48318a0947029d796447b906bd96c2421f128e16235dba1e1e3f1e66a89ec75d1c470a547beed37b64c5d951c5fd3e61142bf70b737b44115a3e9642c582030a5eb1f2084b23321a79d30f3b632feb683b81624970dfb66064eea5062406331411caff7f9e70271a0911ea71dc590f10aae0b77fd45beb06acd96d6ff21142091b5e880010212776afa8044c3b701617337ee114cde72232e30ede7450c5eb6848d6f62d47d4b74615c32a4a5c01ee39bb4ffc576f01c10c2284f1431901ef60ba24f3269b57017f7d30da49f5a555700b37b85fe11e80501aac88041c01b85f7f8fa76a23bd7276f85ac76f29705f6af8185e7da434355f1b82a1731377e67db5555c55ca2aa63f4ee7fc14e8d33d6a9812c97132f6da0938992953e0e8bf1f79ca92504d5c541d3deaad59341a8f28bc5d152a5f6e9f1d4e1b7e0977820851fac5a01ccb4b58536c285e4105fd587cac6ad82386d4e64521fe105c2bfa7f0eaa91593c1a59d84b556adf78a2aab739be8d0d2b70ec806cb5219e3773e369003b17272c04099b4c5cb7a76ad329f01d36ff75569450d13db312b03dafc90827e2ac255bf0fc5d17e4e3974f9e0a3b054f6bfd3432ff1559158d435649319e51fd4a6e2c82b5a1174e2ef74da9b54650088a885d702c082ad4afc65eb21be2198a85e075291aa65754c699534813ee209a0627440ae8370bbcf65721d51d4e700ef1d25718aeff0ba8473e0e44f0482eacfed0495b5aee4bf3b951558eabf6244c574c63d79de9242db6312a9bc24918099dff7d42437500e5f3e76906e86d2ac4f816183322df37af9db47acd829f75a34ee761844d7b597f9e810f2dd4c757ad672131d4641b6376e7b578476e4875de4c536e32de0d1a1c8c9665ec3d26464a8c0d26c4d3d473302e6f74f68ade6f202ec33f23e8c0498536d5146c850f23fb85aa6eb2ec063f0748593b0423aa6cf42f7c3fec3b41250b0b1817b9289357205e205dbaa8cc1186ab324dc3ac073f3ef6476b054ab45cf180cf16ec5d691cd9aecf3f6768850f33ccb111b7fb222e9946932950584877a3394974e3d2a04f142c1d6bd367b868d95d7f3f345ae02af74f79325e945454a0dfef4df2d5232110815b13a8274909f6f8cd0d05b1d75294638a2e0104e624bed96a2ab4c1aa0bbcacb23644859d77786eb24afaa2fa2149cf515469ef8161e600643e237e2114d05707711acd1550da794442699e9a1a6a255e477eb38d364c713b6a7e517b32511b461f25cfba29b3ab5b5d486bbf5184630f7e538b4b2b3a41e37294e46a11fbb29434313ab100935bf523cc7d23267f6ad3d1dfd5cda1eb7a0912bab6cd8ecb5701532d00310b");
        byte[] c2 = IOUtils.hexStringToByteArray(
                "e98d447ee5c39c5a2536fe1acf8ea83c0842be6ef58d050ce95bbe0ce2bb02315e2ca02653811c54606f9067aa9ddb10d2d29706b28ad606d06c963a857ef363faf5955854d0a538d8093f0f3537794be8b4104ad4bcd34366712a4c824a532e64d3f22698afc171d9b9003da8abbc15879a0b4e75ae4b4365f2384f7028504c5a72e81d8a28376a3fb7f80862b4a60c80b63c762913b7154470a93ddee2dc1c7c94d369a5df39258980b82d4e676b70576a102c59ed4e68d3e55e545160a604adbdf420acef9d6386971f509572056b80ac7127ec084a1c17bd5819e47f644e85b80b0ef17659568c2b21640c53175c6625a519f11d5a337c7b6728c0978d377c46911dbb326031224c3444fcfccd13e445174766bcdd01dadfaa3061daea300b9c17276360635eaf41562162062853bc4db2468327b575b466ce57690ba7672f1753593afeed27ff7dac52afc3c40027073844163b056c9343294facbf435207b25e421f6f4a33b8125b2e6dd7035c118da466348ec2562d6f91138dd33504efc022084fbbc55789d00318d4063a4fb677a35963b0ae4835e12400c113bb00c7101227e4227b21241ae353835ec46d674a3017d880b12bed696b5596618370127f9f53ece71728452548713986d71702231d14d8687140e5451b6a0ad57b56f8d7bb739d58761877ac7f320965605ad1e6386fa51b11469638965ec1a75b77f4d6d61d1f099a7695ae9546aa4e7a7782b9483fca8fba466c62357849ca5a66aeb23568907c184ccd281f5416fd657f68fdc977ba928a29ac5ee96f7a7c694ba67aa251f2833161b4024163a89dbf65caeca22199485c4db2723b3cc2c45e1537a1d2652a1fbb6ecb29bf6f08880b55cf3acc346162554ec92f674cc311a352806bef445edefc126e601d4a03253804286eb759dac252424cef926ad720ed416a3f6b0e1918b23eed1d5341d23c3506d3aa3c68997c3c314cb99e517925df398b006e1200bcdf3422c39e1f56ed10349a043c02d435da5b18b26f49d1a50e68fe54954ae4db2e39d92d1a3dcd8f617f453e8407a35d810991a10452c6a9734c013c7e1cff01221cc9ceab502aaa3576d9c4745e15be3e3b01cb22384304e06c2fd6f079eee875791541157302812d628765b22a61fab3447ca60c1c1366203d62b693799e69ab3b69533171fcbacf7b729f85178105a13acd60de6371f41a6265e1cf73a68ef8203e847c61ab1f547b49ec792acf25813371c9c7474b28f846ce27a34f3a98731875d22d3da7ec172e4f56b253769d5075eaf0f71a7e2ca34d6486c66eff310d0e81add02febeb7819612cc152fd53dd4bfe519956c3e2544c9bbd880767a5ca47bf9d24480d5d0e1fe9aa6b028cfe022c7e5129014e8c3b76328dfb4cbdd5a562f9ab8f717c7975778cfb26166a755739c7a16d3e5b23ca65a40dcb513c749b7b0210e213f4637d25b211ec70ed00da2e729020731698b25fec32e73cf33df12201842b794d5fa80ff091ce6e00d6c44f1042fd5b8b4f5776677b8f17cfdf212499ac65155026fb195bde245017fe8e169fb236108e6b201dd4d33479985ec6010ae5951461cf5b0f03d41d3bd1860353bcf22575a7e1e80c0dfb9e4ebe0208099b456632bf0c8b3fab03e2370ed68625d5a43d1f9836c97401147848d6286918e5957104f2a54637d6fe2d68f6d76e607df59d2d3e7abd7fc5b7900416a203438ea0b8192196b5542ea092592d53ef29af01d6710274c752c6b1b52bb9e66b0663432362c985d3668a6d6f591f3649577067bc7397680e28de3851600cad222656759967893c33181a83a94b2b1ad7062173fc0c1b9721140143401f07096e110d3d684bd8416e07fde0dc718b32067916bc2b07c2986d76a1d4093ca45ce420e32e234bcf749c15d2afd34a9230f93cd2e86368986189764b176543352c874a61e75c5dd584d41c5562d021d14e19516cede244339b2102ddfb3b77c2627c2cbcd7541af77ee542ed7c5333de4a512713160757efbf9352e553bf3820536f22c701025ae2349c2aab85751bddbd2d61a4cd09214d5a7f57811a120288fc2c6c1ccf1b6d53cae54c1a2d2629eeb77f55eb2b6f4366448b6cb0b0f397ef997c742ebdcb862f127973583e9dc9627fc9825ddf7a525823a84d");

        InboundHandshake in = new InboundHandshake();
        in.setHandshakeBytes(serverInitialHandshake);
        IoBuffer serverResponse = in.decodeClientRequest1(IoBuffer.wrap(clientRequest1));
        Assert.assertArrayEquals(s0s1s2, serverResponse.array());
        Assert.assertTrue(in.decodeClientRequest2(IoBuffer.wrap(c2)));

    }

    @Test
    public void testHandshakeArrayOutOfBounds() throws InterruptedException, ExecutionException {
        log.info("\n testHandshakeArrayOutOfBounds");
        final AtomicInteger failures = new AtomicInteger(0);
        final AtomicInteger threadCount = new AtomicInteger(100);
        // Convenience
        int count = threadCount.get();
        ExecutorService executor = Executors.newFixedThreadPool(count / 4);
        for (int i = 0; i < count; i++) {
            @SuppressWarnings("unused")
            Future<Boolean> future = executor.submit(new Callable<Boolean>() {

                @Override
                public Boolean call() throws Exception {
                    Boolean failed = Boolean.FALSE;
                    try {
                        InboundHandshake in = new InboundHandshake();
                        in.getHandshakeBytes();
                    } catch (Exception e) {
                        e.printStackTrace();
                        failed = Boolean.TRUE;
                        failures.incrementAndGet();
                    } finally {
                        threadCount.decrementAndGet();
                    }
                    return failed;
                }

            });
        }
        do {
            Thread.sleep(100L);
        } while (threadCount.get() > 0);
        log.info("Wait completed, failures: {}", failures.get());
        Assert.assertEquals(0, failures.get());
    }

}
